import csv
from curses.ascii import isupper
from django.conf import settings
from analysis.models import *
from fundinfo.models import *
from trading.models import *
from django.core.cache import caches,cache
from fundinfo.fundlist import FundBasicInfo
import pandas as pd
import numpy as np
# import ffn

# 计算最大回撤
# "date", "jjjz", "ljjz"
def calculateMaxDrawdown(df):
    if len(df) < 2:
        return 0.0  # 如果投资组合净值数据不足两个点，无法计算回撤
    
    # 计算最高点
    peak1 = df.iloc[0]['ljjz']
    peak2 = df.iloc[0]['jjjz']

    # 初始化最大回撤为0
    max_drawdown = 0.0

    last1 = peak1
    for index, row in df.iterrows():
        if row['ljjz'] > peak1:
            peak1 = row['ljjz']  # 更新最高点
            peak2 = row['jjjz']  # 更新最高点
            last1 = peak1
        else:
            if last1 < row['ljjz']: # 高于上一次的，肯定算出来比之前的小，可以跳过
                continue
            drawdown = (peak1 - row['ljjz']) / peak2
            if drawdown > max_drawdown:
                max_drawdown = drawdown
            last1 = row['ljjz']

    return round(max_drawdown * 100, 2)




class TradingFundInfo:
    code = ""
    name = ""
    type = ""
    risk = ""
    cost = 0.0    # 持有成本
    income = 0.0  # 持有收益
    incomeRate = 0.0    # 持有收益率
    units = 0.0   # 持有份额
    incomeTotal = 0.0
    priceMonitor = False

class FundIncomeInfo:
    date = ""
    jjjz = 0.0
    ljjz = 0.0
    rate = 0.0
    units = 0.0
    totalCost = 0.0
    cost = 0.0
    incomeTotal = 0.0
    income = 0.0
    incomePercent = 0.0
    incomeDay = 0.0

class IncomeMonth:
    date = ""
    income = 0.0


class YearIncomeRate:
    year1 = 0.0
    year3 = 0.0
    year5 = 0.0

class BasicData:
    trading_fund_lst_file = settings.ANALYSIS_DIR + "/trading_fund_lst.csv"
    total_income_month = settings.ANALYSIS_DIR + "/month_income.csv"
    price_path = settings.FUND_PRICE_DIR
    tradingDaysOneYear = 245

    def saveTradingFundLst(lst):
        fundLst = []

        for fund in lst:
            fundLst.append([fund.code, fund.name, fund.type, fund.risk, fund.incomeTotal,
                            fund.income, fund.units, fund.incomeRate, fund.cost])

        # code, name, type, risk, incomeTotal, income, incomeRate, cost
        with open(BasicData.trading_fund_lst_file, "w", newline='') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerow(["code", "name", "type", "risk", "incomeTotal", "income", "units", "incomeRate", "cost"])
            writer.writerows(fundLst)
            print("save trading fund list end.")
        return

    @staticmethod
    def getTradingFundCodeLst():
        from analysis.price_analysis import PriceAnalysis
        csvReader = None
        try:
            csvReader = csv.reader(open(BasicData.trading_fund_lst_file, encoding='utf-8'))
        except:
            return None

        fundLst = []
        count = 0
        for line in csvReader:
            if count == 0:
                count = count + 1
                continue
            fundLst.append(line[0])
        return fundLst

    @staticmethod
    def getTradingFundLst():
        funds = FundInfoBrief.objects.all()
        if funds == None or len(funds) == 0:
            return None

        from analysis.price_analysis import PriceAnalysis

        fundLst = []
        for dbInfo in funds:
            info = TradingFundInfo()
            info.code = dbInfo.fund.code
            info.name = dbInfo.fund.name
            info.type = dbInfo.fund.type
            info.risk = dbInfo.fund.risk
            info.incomeTotal = round(dbInfo.totalIncome)
            info.income = round(dbInfo.curIncome)
            info.units = dbInfo.curUnits
            info.incomeRate = dbInfo.curIncomeRate
            info.cost = dbInfo.curCost #dbInfo.curValue - dbInfo.curIncome
            #price = PriceAnalysis(info.code)
            #price.readPriceData()
            #monitorMaxRate, monitorMaxDay, monitorMinRate, monitorMinDay, monitorResult = price.rateMonitor(None)
            info.priceMonitor = dbInfo.alarm
            fundLst.append(info)
        return fundLst

        # 采用中间文件的方式
        # csvReader = None
        # try:
        #     csvReader = csv.reader(open(BasicData.trading_fund_lst_file, encoding='utf-8'))
        # except:
        #     return None

        # count = 0
        # for line in csvReader:
        #     if count == 0:
        #         count = count + 1
        #         continue
        #     fund = TradingFundInfo()
        #     fund.code = line[0]
        #     fund.name = line[1]
        #     fund.type = line[2]
        #     fund.risk = line[3]
        #     fund.incomeTotal = float(line[4])
        #     fund.income = float(line[5])
        #     fund.units = float(line[6])
        #     fund.incomeRate = float(line[7])
        #     fund.cost = float(line[8])
        #     price = PriceAnalysis(fund.code)
        #     price.readPriceData()
        #     monitorMaxRate, monitorMaxDay, monitorMinRate, monitorMinDay, monitorResult = price.rateMonitor(None)
        #     fund.priceMonitor = monitorResult
        #     fundLst.append(fund)
        # return fundLst

    @staticmethod
    def getTradingFundLstFromDB():
        fundLst = []
        codeLst = {}
        tradings = Trading.objects.all()
        for item in tradings:
            if codeLst.__contains__(item.code):
                continue       

            codeLst[item.code] = ""
            fund = BasicData.getTradingFundInfo(item.code)
            fundLst.append(fund)
        toBeLst = BasicData.getToBeTradingFundLstFromDB()
        for item in toBeLst:
            trading = Trading()
            trading.code = item.code
            trading.name = item.name
            trading.type = item.type
            trading.risk = item.risk
            fundLst.append(trading)
        return fundLst

    @staticmethod
    def getToBeTradingFundLstFromDB():
        fundLst = FundName.objects.all().filter(selfType=2)
        return fundLst

    #FundBasicInfo
    @staticmethod
    def getTradingFundBasicInfoFromDB():
        fundLst = []
        codeLst = {}
        tradings = Trading.objects.all()
        for item in tradings:
            if codeLst.__contains__(item.code):
                continue       

            codeLst[item.code] = ""
            fund = BasicData.getTradingFundInfo(item.code)
            fundLst.append(fund)
        return fundLst

    @staticmethod
    def getTradingFundInfoByCustomCategorys():
        fundLst = []
        codeLst = {}
        info = CustomCategory.objects.all()
        for item in info:
            code = item.fund.code
            if codeLst.__contains__(code):
                continue       

            codeLst[code] = ""
            fund = BasicData.getTradingFundInfo(code)
            fundLst.append(fund)
        return fundLst        

    @staticmethod
    def getTradingFund(code):
        lst = BasicData.getTradingFundLst()
        for fund in lst:
            if fund.code == code:
                return fund
        return None

    @staticmethod
    def getFundPrice(code):
        filename = settings.ANALYSIS_DIR + "/trading_fund_lst.csv"

    def getTradingFundInfo(code):
        fund = TradingFundInfo()

        info = FundName.objects.filter(code=code)
        if info == None or len(info) == 0:
            return None
        
        info = info[0]
        fund.code = code
        fund.name = info.name
        fund.type = info.type
        fund.risk = info.risk
        return fund
    
    @staticmethod
    def getTotalIncomMonthList():
        return BasicData.getFundMonthIncomeFromFile(BasicData.total_income_month)

    @staticmethod
    def getFundMonthIncome(code):
        filename = settings.ANALYSIS_DIR + "/month_income_" + code + ".csv"
        lst = BasicData.getFundMonthIncomeFromFile(filename)

        # 去掉前面的无效数据
        index = 0
        for i in range(0, len(lst)):
            if lst[i].income != 0:
                return lst[i:]
        return lst

    @staticmethod
    def getFundMonthIncomeFromFile(filename):
        csvReader = None
        try:
            csvReader = csv.reader(open(filename, encoding='utf-8'))
        except:
            return None
        
        incomeLst = []
        count = 0
        for line in csvReader:
            if count == 0:
                count = count + 1
                continue
            income = IncomeMonth()
            income.date = line[0]
            income.income = float(line[1])
            incomeLst.append(income)
        return incomeLst        

    @staticmethod
    def getFundIncomeInfo(code):
        filename = settings.ANALYSIS_DIR + "/income_" + code + ".csv"
        csvReader = None
        try:
            csvReader = csv.reader(open(filename, encoding='utf-8'))
        except:
            return None

        incomeLst = []
        count = 0
        start = False
        for line in csvReader:
            if count == 0:
                count = count + 1
                continue
            if round(float(line[4]), 2) == 0 and start == False: # 去除前面未开始交易的无效数据
                continue
            start = True
            income = FundIncomeInfo()
            income.date = line[0]
            income.jjjz = round(float(line[1]), 2)
            income.ljjz = round(float(line[2]), 2)
            income.rate = round(float(line[3]), 2)
            income.units = round(float(line[4]), 2)
            income.totalCost = round(float(line[5]), 2)
            income.cost = round(float(line[6]), 2)
            income.incomeTotal = round(float(line[7]), 2)
            income.income = round(float(line[8]), 2)
            income.incomePercent = round(float(line[9]), 2)
            income.incomeDay = round(float(line[10]), 2)
            incomeLst.append(income)
        return incomeLst

    @staticmethod
    def getYearIncomeRate(code):
        filename = settings.ANALYSIS_DIR + "/year_rate.csv"
        csvReader = None
        try:
            csvReader = csv.reader(open(filename, encoding='utf-8'))
        except:
            return None

        rate = YearIncomeRate()
        for line in csvReader:
            if line[0] == code:
                rate.year1 = round(float(line[1]), 2)
                rate.year3 = round(float(line[2]), 2)
                rate.year5 = round(float(line[3]), 2)
        return rate

    @staticmethod
    def getCivilYearRate(code):
        filename = settings.ANALYSIS_DIR + "/civil_year_rate.csv"
        csvReader = None
        try:
            csvReader = csv.reader(open(filename, encoding='utf-8'))
        except:
            return None
        
        for line in csvReader:
            if line[0] == code:
                return line[1:]
        return []

    @staticmethod
    def gtMaxDrawDown(code, days, ignoreDays):
        #return 1
        filename = BasicData.price_path + "/" + code + ".csv"
        tmp = pd.read_csv(filename)

        if len(tmp) <= days - ignoreDays:
            return 0.0

        skip = 0
        if len(tmp) > days:
            skip = len(tmp) - days

        df = pd.read_csv(filename, skiprows = skip)
        df.columns = ["date", "jjjz", "ljjz"] #读取到的csv文件没有表头，重新设置表头
        return calculateMaxDrawdown(df)

        # df['rate'] = 0.0 # 通过ffn库对收益率计算最大回撤
        # rate = [0.0]
        # for i in range(1, len(df)):
        #     # df["rate"][i] = format((df["ljjz"][i] - df["ljjz"][i - 1]) / df["jjjz"][i - 1], ".2") # format的结果是str类型，直接赋值会报告警
        #     rate.append((df["ljjz"][i] - df["ljjz"][i - 1]) / df["jjjz"][i - 1])
        #     pass
    
        # df.loc[:, "rate"] = rate
        # r = pd.Series(df['rate'])
        # # value = (1 + r).cumprod() #扩展恢复以1为基数的净值（考虑分红情况）
        # #return round(0 - ffn.calc_max_drawdown(value) * 100, 2) 
        # # 计算最大回撤，考虑时序
        
        # print('最大回撤', calculateMaxDrawdown(df))
        # return maxDrawDown(r)

        # fitten code写的,代替calc_max_drawdown的ffn库代码
        # df['cum_return'] = value.pct_change() #计算收益率序列
        # df['drawdown'] = 1 - df['cum_return'] #计算回撤序列 
        # df['drawdown_pct'] = df['drawdown'].rolling(days).apply(lambda x: max(x) - min(x)) #计算回撤率序列
        # df['drawdown_pct'] = df['drawdown_pct'].fillna(0) #用0填充空值
        # maxDrawDown = df['drawdown_pct'].max() #获取最大回撤率
        # return round(maxDrawDown * 100, 2)

    @staticmethod
    def getMaxDrawDownYear(code):
        return BasicData.gtMaxDrawDown(code, BasicData.tradingDaysOneYear, BasicData.tradingDaysOneYear)

        # filename = BasicData.price_path + "/" + code + ".csv"
        # tmp = pd.read_csv(filename)

        # skip = 0
        # if len(tmp) > BasicData.tradingDaysOneYear:
        #     skip = len(tmp) - BasicData.tradingDaysOneYear

        # df = pd.read_csv(filename, skiprows = skip)
        # df.columns = ["date", "jjjz", "ljjz"] #读取到的csv文件没有表头，重新设置表头

        # df['rate'] = 0.0 # 通过ffn库对收益率计算最大回撤
        # for i in range(1, len(df)):
        #     df["rate"][i] = format((df["ljjz"][i] - df["ljjz"][i - 1]) / df["jjjz"][i - 1], ".2")
        #     pass

        # r = pd.Series(df['rate'])
        # value = (1 + r).cumprod() #扩展恢复以1为基数的净值（考虑分红情况）
        # return round(ffn.calc_max_drawdown(value) * 100, 2)
    
    @staticmethod
    def getAvgPrice(code, price, days):
        sum = 0.0
        count = 0
        for tmp in reversed(price):
            sum = tmp.ljjz + sum
            count = count + 1
            days = days - 1
            if days == 0:
                break
        
        return round(sum / count - price[-1].ljjz + price[-1].jjjz, 2)   

class CategoryIncomeData:
    name = ""
    value = 0.0
    income = 0.0
    incomeTotal = 0.0
    incomeRate = 0.0
    last6MonthIncome = []
    incomePre1Y = 0.0

class PreCategoryTools:
    def getPreDefineDict():
        lst = {}
        lst['股票基金'] = '股票'
        lst['债券基金'] = '债券'
        lst['FOF基金'] = 'FOF'
        lst['指数基金'] = '指数'
        lst['混合基金'] = '混合'
        return lst

    def getPreDefineCategoryNames():
        lst = ['股票基金', '债券基金', 'FOF基金', '指数基金', '混合基金']
        return lst

    def isPreDefineCategoryNames(name):
        lst = PreCategoryTools.getPreDefineCategoryNames()
        for n in lst:
            if n == name:
                return True

        return False

    def getCategoryType(category):
        lst = PreCategoryTools.getPreDefineDict()
        return lst[category]
    
    def getFundsByCategoryName(category):
        categoryType = PreCategoryTools.getCategoryType(category)
        funds = FundName.objects.filter(type=categoryType).all()
        lst = []
        for fund in funds:
            lst.append(fund)
        return lst

class CustomCategoryTools:
    def getCustomCategoryNames():
        lst = CustomCategoryName.objects.all()
        if lst == None or len(lst) == 0:
            return []
        
        lst2 = []
        for item in lst:
            lst2.append(item)

        lstPreDefine =  PreCategoryTools.getPreDefineCategoryNames()
        
        lst = sorted(lst, key=lambda category: category.priority, reverse=True)
        lst2 = []
        for item in lst:
            lst2.append(item.name)
        lst2 = lstPreDefine + lst2
        return lst2

    def getCustomCategoryList():
        lst = CustomCategory.objects.all()
        if lst == None or len(lst) == 0:
            return []
        categorys = {}
        for item in lst:
            categorys[item.name] = item.fund
        names = []
        for item in categorys:
            names.append(item)
        return names

    def getFundsByCategory(category):
        funds = None
        if PreCategoryTools.isPreDefineCategoryNames(category):
            funds = PreCategoryTools.getFundsByCategoryName(category)
        else:
            lst = CustomCategory.objects.all().filter(name=category)
            if lst == None or len(lst) == 0:
                return []
            funds = []
            for item in lst:
                funds.append(item.fund)
        return funds

    def getCategoryIncomeData(category):
        funds = CustomCategoryTools.getFundsByCategory(category)
        # tradings = BasicData.getTradingFundLst()

        cost = 0.0
        value = 0.0
        income = 0.0
        incomeTotal = 0.0
        incomePre1Y = 0.0
        monthIncome = [0, 0, 0, 0, 0, 0]

        for fund in funds:
            code = fund.code

            infoBrief = FundInfoBriefData()
            fundInfo = infoBrief.getExsitRecord(code)
            if fundInfo == None or fundInfo.curIncome == None:
                continue
            incomeTotal = incomeTotal + fundInfo.totalIncome
            income = income + fundInfo.curIncome
            value = value + fundInfo.curValue
            monthIncome[0] = monthIncome[0] + fundInfo.income1M
            monthIncome[1] = monthIncome[1] + fundInfo.income2M
            monthIncome[2] = monthIncome[2] + fundInfo.income3M
            monthIncome[3] = monthIncome[3] + fundInfo.income4M
            monthIncome[4] = monthIncome[4] + fundInfo.income5M
            monthIncome[5] = monthIncome[5] + fundInfo.income6M
            if fundInfo.incomePre1Y == None:
                fundInfo.incomePre1Y = 0.0
            incomePre1Y = incomePre1Y + fundInfo.incomePre1Y

        monthIncome[0] = round(monthIncome[0], 2)
        monthIncome[1] = round(monthIncome[1], 2)
        monthIncome[2] = round(monthIncome[2], 2)
        monthIncome[3] = round(monthIncome[3], 2)
        monthIncome[4] = round(monthIncome[4], 2)
        monthIncome[5] = round(monthIncome[5], 2)

        cost = value - income
        incomeRate = 0
        if cost != 0:
            incomeRate = income * 100 / cost

        categoryIncome = CategoryIncomeData()
        categoryIncome.name = category
        categoryIncome.value = round(value)
        categoryIncome.income = round(income)
        categoryIncome.incomeTotal = round(incomeTotal)
        categoryIncome.incomeRate = round(incomeRate, 2)
        categoryIncome.last6MonthIncome = monthIncome
        categoryIncome.incomePre1Y = round(incomePre1Y, 2)

        return categoryIncome

    def getCategorysIncomeData():
        categorys = []
        lst = CustomCategoryTools.getCustomCategoryNames()
        for item in lst:
            category = CustomCategoryTools.getCategoryIncomeData(item)
            categorys.append(category)
        return categorys

class FundInfoBriefData:
    def getFundName(self, code):
        fund = FundName.objects.filter(code=code).all()
        if fund == None or len(fund) == 0:
            return None
        return fund[0]

    def getExsitRecord(self, code):
        fundInfo = FundInfoBrief.objects.filter(fund__code=code)
        if fundInfo == None or len(fundInfo) == 0:
            return None
        else:
            return fundInfo[0]        

    def getRecord(self, code):
        fundInfo = FundInfoBrief.objects.filter(fund__code=code)
        if fundInfo == None or len(fundInfo) == 0:
            fund = FundName.objects.filter(code=code).all()
            if fund == None or len(fund) == 0:
                return None
            fundInfo = FundInfoBrief()
            fundInfo.fund = fund[0]
            return fundInfo
        else:
            return fundInfo[0]

    def refreshFundIncome(self, code):
        tradingFund = BasicData.getTradingFund(code)

        fundInfo = self.getRecord(code)
        if fundInfo == None:
            return
        
        fundInfo.totalIncome = tradingFund.incomeTotal
        fundInfo.curIncome = tradingFund.income
        fundInfo.curIncomeRate = tradingFund.incomeRate
        fundInfo.curUnits = tradingFund.units
        #fundInfo.curValue = tradingFund.totalCost + tradingFund.income

        fundInfo.save()

    def refreshCurIncome(self, code, date, rate, income):
        fundInfo = self.getRecord(code)
        if fundInfo == None:
            return

        fundInfo.incomeDayDate = date
        fundInfo.incomeDayRate = rate
        fundInfo.incomeDay = income.incomeDay
        fundInfo.totalIncome = income.incomeTotal
        fundInfo.curCost = income.totalCost
        fundInfo.curIncome = income.income
        fundInfo.curIncomeRate = income.incomePercent
        fundInfo.curUnits = income.units
        fundInfo.curValue = round(income.totalCost + income.income, 2)
        self.refreshIncomePre1Y(fundInfo)
        fundInfo.save()

    def refreshMonthIncomeRate(self, code, incomeRate1M, incomeRate3M, incomeRate6M):
        fundInfo = self.getRecord(code)
        if fundInfo == None:
            return
        fundInfo.incomeRate1M = incomeRate1M
        fundInfo.incomeRate3M = incomeRate3M
        fundInfo.incomeRate6M = incomeRate6M
        fundInfo.save()       

    def refreshYearIncomeRate(self, code, incomeRate1Y, incomeRate2Y, incomeRate3Y, incomeRate5Y):
        fundInfo = self.getRecord(code)
        if fundInfo == None:
            return
        fundInfo.incomeRate1Y = incomeRate1Y
        fundInfo.incomeRate2Y = incomeRate2Y
        fundInfo.incomeRate3Y = incomeRate3Y
        fundInfo.incomeRate5Y = incomeRate5Y
        self.refreshIncomePre1Y(fundInfo)
        fundInfo.save()

    def refreshMonthIncome(self, code, monthIncome):
        fundInfo = self.getRecord(code)
        if fundInfo == None:
            return  
        
        fundInfo.income1M = 0.0
        fundInfo.income2M = 0.0
        fundInfo.income3M = 0.0
        fundInfo.income4M = 0.0
        fundInfo.income5M = 0.0
        fundInfo.income6M = 0.0
        if len(monthIncome) >= 1:
            fundInfo.income1M = monthIncome[-1][1]
        if len(monthIncome) >= 2:
            fundInfo.income2M = monthIncome[-2][1]  
        if len(monthIncome) >= 3:
            fundInfo.income3M = monthIncome[-3][1]  
        if len(monthIncome) >= 2:
            fundInfo.income4M = monthIncome[-4][1]  
        if len(monthIncome) >= 2:
            fundInfo.income5M = monthIncome[-5][1]  
        if len(monthIncome) >= 2:
            fundInfo.income6M = monthIncome[-6][1]  
        fundInfo.save()      
    
    def refreshMaxDrawDown(self, code, d1, d2, d3, d5):
        fundInfo = self.getRecord(code)
        if fundInfo == None:
            return
        
        fundInfo.maxDrawDown1Y = d1
        fundInfo.maxDrawDown2Y = d2
        fundInfo.maxDrawDown3Y = d3
        fundInfo.maxDrawDown5Y = d5
        fundInfo.icomeDrawDownRate1Y = round(fundInfo.incomeRate1Y / fundInfo.maxDrawDown1Y, 2)
        fundInfo.save()
    
    def refreshPriceAvg(self, code, avg1M, avg6M, avg1Y):
        fundInfo = self.getRecord(code)
        if fundInfo == None:
            return

        fundInfo.priceAvg1M = avg1M
        fundInfo.priceAvg6M = avg6M
        fundInfo.priceAvg1Y = avg1Y
        fundInfo.save()

    def refreshIncomePre1Y(self, fundInfo):
        incomePre1Y = 0.0
        if fundInfo.incomeRate1Y == None:
            pass
        elif fundInfo.incomeRate1Y != 0:
            incomePre1Y = fundInfo.incomeRate1Y * fundInfo.curValue / 100
        elif fundInfo.incomeRate6M != 0:
            incomePre1Y = fundInfo.incomeRate6M * 2 * fundInfo.curValue / 100
        elif fundInfo.incomeRate3M != 0:
            incomePre1Y = fundInfo.incomeRate3M * 4 * fundInfo.curValue / 100
        elif fundInfo.incomeRate1M != 0:
            incomePre1Y = fundInfo.incomeRate1M * 12 * fundInfo.curValue / 100
        
        fundInfo.incomePre1Y = round(incomePre1Y, 2)
        #fundInfo.

        return

    def refreshAlarm(self, code, alarm, text):
        fundInfo = self.getRecord(code)
        if fundInfo == None:
            return

        fundInfo.alarm = alarm
        fundInfo.alarmText = text
        fundInfo.save()
        return        





                
